﻿using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Timing;
using YAKEJI.Abp.Domain.Repositories;
using YAPACS.BusinessObjects.Exams;
using YAPACS.BusinessObjects.Managements.Devices;
using YAPACS.BusinessObjects.Services;
using YAPACS.Organizations;
using YAPACS.RisServer.Abstractions;
using YAPACS.RisServer.Services.Dtos;

namespace YAPACS.RisServer.Services;

public class ExamQueueService(IClock clock, IObjectMapper objectMapper,
    IGroupCodeProvider groupCodeProvider,
    IRepository<ExamQueuing, Guid> examQueuingRepository,
    IRepository<CallingQueuing, Guid> callingQueuingRepository,
    IRepository<Station, Guid> stationRepository,
    IRepository<Department, Guid> departmentRepository)
    : DomainService, IExamQueueService, ITransientDependency
{
    private readonly IClock _clock = clock;
    private readonly IObjectMapper _objectMapper = objectMapper;
    private readonly IGroupCodeProvider _groupCodeProvider = groupCodeProvider;
    private readonly IRepository<ExamQueuing, Guid> _examQueuingRepository = examQueuingRepository;
    private readonly IRepository<CallingQueuing, Guid> _callingQueuingRepository = callingQueuingRepository;
    private readonly IRepository<Station, Guid> _stationRepository = stationRepository;
    private readonly IRepository<Department, Guid> _departmentRepository = departmentRepository;

    public async Task<ExamQueuing> CreateExamQueuingAsync(Exam exam, bool isArrived = false)
    {
        var examQueuing = exam.ExamQueuing;
        if (examQueuing == null || exam.ExamDevice != examQueuing.Device || exam.ExamCategory != examQueuing.ExamCategory)
        {
            if (examQueuing != null)
            {
                // 判断设备和检查类别是否发生变化，如果发生变化，已经存在的队列需要删除重新添加
                await _examQueuingRepository.DeleteAsync(examQueuing);
            }
            examQueuing = await CreateExamQueueAsync(exam, isArrived);
        }
        return examQueuing;
    }

    private async Task<ExamQueuing> CreateExamQueueAsync(Exam exam, bool isArrived)
    {
        var examDevice = exam.ExamDevice;
        var examCategory = exam.ExamCategory;

        var examAppointment = exam.ExamAppointment;
        var now = _clock.Now;

        ExamQueuing examQueuing = null;
        if (examAppointment == null || examAppointment.StartTime.Date == now.Date || isArrived)
        {
            examQueuing = await _examQueuingRepository.NewEntityAsync();
            examQueuing.Status = ExamQueuingStatus.Wait;
            examQueuing.Exam = exam;
            examQueuing.ExamCategory = exam.ExamCategory;

            var hour = examAppointment != null ? examAppointment.StartTime.Hour : now.Hour;
            if (hour > 12)
            {
                examQueuing.StartTime = now.Date.AddHours(12);
            }
            else
            {
                examQueuing.StartTime = now.Date;
            }

            examQueuing.IsArrive = true;

            examQueuing.Device = examDevice;
            examQueuing.ExamCategory = examCategory;

            // 检查队列当天有效
            examQueuing.EndTime = examQueuing.StartTime.AddDays(1).Date.AddSeconds(-1);

            var device = examQueuing.Device;
            var category = examQueuing.ExamCategory;

            var startTime = examQueuing.StartTime;
            var endTime = examQueuing.EndTime;

            var query = await _examQueuingRepository.GetQueryableAsync();

            query = query.Where(x => x.StartTime >= startTime && x.StartTime <= endTime);
            if (device != null)
            {
                query = query.Where(x => x.Device == device);
            }
            else
            {
                query = query.Where(x => x.ExamCategory == category);
            }

            var order = await AsyncExecuter.FirstOrDefaultAsync(query.OrderByDescending(x => x.Order).Select(x => x.Order));
            examQueuing.Order = order + ExamQueuing.ORDER_SPACING;
        }

        return examQueuing;
    }

    public async Task<IList<WaitingQueueDto>> GetDepartmentWaitingQueuesAsync(string departmentCode)
    {
        List<WaitingQueueDto> waitingQueueDtos = [];

        var department = await _departmentRepository.FirstOrDefaultAsync(e => e.Code == departmentCode && e.IsActive);
        if (department == null)
        {
            return waitingQueueDtos;
        }

        var groupCode = _groupCodeProvider.GetGroupCode();

        var devices = department.GetDevices()
            .WhereIf(!string.IsNullOrWhiteSpace(groupCode), e => e.DeviceGroup?.Code == groupCode)
            .ToList();

        var now = Clock.Now;

        foreach (var device in devices)
        {
            var waitingQueueDto = waitingQueueDtos.FirstOrDefault(w => w.RoomName == device.CompoundName);
            if (waitingQueueDto == null)
            {
                waitingQueueDto = new WaitingQueueDto
                {
                    RoomName = device.CompoundName
                };

                waitingQueueDtos.Add(waitingQueueDto);
            }

            var query = (await _examQueuingRepository.GetQueryableAsync())
                .Where(e => e.IsArrive && e.Device == device && e.StartTime <= now && e.EndTime > now)
                .OrderBy(e => e.Order)
                .ThenBy(e => e.CreationTime);

            // 正在检查
            var checkQueues = await AsyncExecuter.ToListAsync(query
                .Where(e => e.Status == ExamQueuingStatus.Check || e.Status == ExamQueuingStatus.Ready)
                .Where(e => e.Exam.Report == null || e.Exam.Report.ReportStatus != ReportStatus.Completed)
            );
            waitingQueueDto.ReadyPatients.AddRange(checkQueues.Select(_objectMapper.Map<ExamQueuing, WaitingPatientDto>));

            // 等候检查
            var waitingQueues = await AsyncExecuter.ToListAsync(query
                .Where(e => e.Status == ExamQueuingStatus.Wait)
                .Where(e => e.Exam.Report == null || e.Exam.Report.ReportStatus != ReportStatus.Completed)
            );
            waitingQueueDto.WaitingPatients.AddRange(waitingQueues.Select(_objectMapper.Map<ExamQueuing, WaitingPatientDto>));
        }

        return waitingQueueDtos;
    }

    public async Task<IList<CallingQueuingDto>> GetDepartmentCallingQueuesAsync(string departmentCode)
    {
        var startTime = Clock.Now.Date;
        var endTime = Clock.Now.Date.AddDays(1);

        var groupCode = _groupCodeProvider.GetGroupCode();

        var callingQueuingQuery = await _callingQueuingRepository.GetQueryableAsync();
        callingQueuingQuery = callingQueuingQuery
            .Where(q => q.CreationTime >= startTime && q.CreationTime < endTime)
            .Where(q => q.ExamQueuing.Exam.Department.Code == departmentCode)
            .WhereIf(!string.IsNullOrWhiteSpace(groupCode), q => q.ExamQueuing.Device.DeviceGroup.Code == groupCode)
            .OrderBy(q => q.CreationTime);

        var callingQueuingList = await AsyncExecuter.ToListAsync(callingQueuingQuery);
        return callingQueuingList.Select(q => new CallingQueuingDto
        {
            Id = q.Id,
            PatientName = q.ExamQueuing.Exam.Patient.Name,
            CallingNumber = q.ExamQueuing.CallNumber,
            RoomName = q.ExamQueuing.Device.Station.Room.Name,
            CallingTimes = q.CallingTimes,
            CallingContent = q.CallingContent,
        }).ToList();
    }

    public async Task<IList<WaitingQueueDto>> GetStationWaitingQueuesAsync(string stationCode)
    {
        List<WaitingQueueDto> waitingQueueDtos = [];

        var station = await _stationRepository.FirstOrDefaultAsync(e => e.Code == stationCode && e.IsActive);
        if (station == null)
        {
            return waitingQueueDtos;
        }

        var now = Clock.Now;

        foreach (var device in station.Devices)
        {
            var waitingQueueDto = waitingQueueDtos.FirstOrDefault(w => w.RoomName == device.CompoundName);
            if (waitingQueueDto == null)
            {
                waitingQueueDto = new WaitingQueueDto
                {
                    RoomName = device.CompoundName
                };

                waitingQueueDtos.Add(waitingQueueDto);
            }

            var query = (await _examQueuingRepository.GetQueryableAsync())
                .Where(e => e.IsArrive && e.Device == device && e.StartTime <= now && e.EndTime > now)
                .OrderBy(e => e.Order)
                .ThenBy(e => e.CreationTime);

            // 正在检查
            var checkQueues = await AsyncExecuter.ToListAsync(query
                .Where(e => e.Status == ExamQueuingStatus.Check || e.Status == ExamQueuingStatus.Ready)
                .Where(e => e.Exam.Report == null || e.Exam.Report.ReportStatus != ReportStatus.Completed)
            );
            waitingQueueDto.ReadyPatients.AddRange(checkQueues.Select(_objectMapper.Map<ExamQueuing, WaitingPatientDto>));

            // 等候检查
            var waitingQueues = await AsyncExecuter.ToListAsync(query
                .Where(e => e.Status == ExamQueuingStatus.Wait)
                .Where(e => e.Exam.Report == null || e.Exam.Report.ReportStatus != ReportStatus.Completed)
            );
            waitingQueueDto.WaitingPatients.AddRange(waitingQueues.Select(_objectMapper.Map<ExamQueuing, WaitingPatientDto>));
        }

        return waitingQueueDtos;
    }

    public async Task<IList<CallingQueuingDto>> GetStationCallingQueuesAsync(string stationCode)
    {
        var startTime = Clock.Now.Date;
        var endTime = Clock.Now.Date.AddDays(1);

        var callingQueuingQuery = await _callingQueuingRepository.GetQueryableAsync();
        callingQueuingQuery = callingQueuingQuery
            .Where(q => q.CreationTime >= startTime && q.CreationTime < endTime)
            .Where(q => q.ExamQueuing.Device.Station.Code == stationCode)
            .OrderBy(q => q.CreationTime);

        var callingQueuingList = await AsyncExecuter.ToListAsync(callingQueuingQuery);
        return callingQueuingList.Select(q => new CallingQueuingDto
        {
            Id = q.Id,
            PatientName = q.ExamQueuing.Exam.Patient.Name,
            CallingNumber = q.ExamQueuing.CallNumber,
            RoomName = q.ExamQueuing.Device.Station.Room.Name,
            CallingTimes = q.CallingTimes,
            CallingContent = q.CallingContent,
        }).ToList();
    }

    public async Task RemoveCallingQueueAsync(Guid queueId)
    {
        await _callingQueuingRepository.DeleteAsync(queueId);
    }
}
